AWS再入門ブログリレー Amazon ECS編
どうも、もこ@札幌オフィスです。
当エントリは弊社コンサルティング部による『AWS 再入門ブログリレー 2020』の 15日目のエントリです。
このブログリレーの企画は、普段 AWS サービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、 今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。AWS をこれから学ぼう!という方にとっては文字通りの入門記事として、またすでに AWS を活用されている方にとっても AWS サービスの再発見や 2020 年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。
では、さっそくいってみましょう。 15日目のテーマは『Amazon ECS』です。
Amazon ECSとは?
Amazon ECSとは「フルマネージドなコンテナオーケストレーションサービス」で、物凄くざっくり言うと「EC2上にコンテナを展開してALBなどと連携してトラフィックを制御して、コンテナのアップデートも良い感じに出来るサービス」です。
Amazon ECSを利用する事でAWS環境でコンテナのクラスターを簡単に作成させることができ、AutoScalingを利用して負荷に応じたコンテナのスケーリングなどもできるサービスになっています。
基本的にはECS Agentが導入されたAMIをベースにEC2のAutoScaling構成を組み、EC2をクラスターに参加させると、ECSからコンテナの実行・停止・デプロイ・各種AWSサービスとの連携が出来るようになります。
上記の図のように、ECSでは主に「クラスター」「サービス」「タスク」の3つで構成されています。それぞれ触って入門していきましょう。
ECSを実際に触ってみる
ECSクラスターを用意してみる
早速となりますが、ECSクラスターに再入門していきましょう。
ECSクラスターは名前の通りECSの骨組みのような物で、ECSクラスターの上に後述する「EC2インスタンス」を起動した上で「タスク定義」を元に作成される「サービス」から「タスク(コンテナ)」をクラスター内のインスタンスにデプロイする事が出来ます。
実践あるのみという事で、ECSのクラスターを作ってみます。
AWS CDKを利用する事で簡単に構築する事ができるので、AWS CDK(TypeScript)の初期設定をしていきます。
$ mkdir ecs $ cd ecs $ npm install -g aws-cdk $ cdk init app --language typescript $ npm install @aws-cdk/aws-ec2 @aws-cdk/aws-ecs @aws-cdk/aws-elasticloadbalancingv2
cdk initを実行した後に作成される lib/ecs-stack.ts
を下記のように編集します。
import * as cdk from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; export class EcsStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC作成 const vpc = new ec2.Vpc(this, 'ecs-vpc', { cidr: "10.0.0.0/16" }); // ECS Cluster作成 const cluster = new ecs.Cluster(this, 'cluster', { vpc }); } }
編集が終わりましたら、早速AWS環境にデプロイしていきましょう。
$ cdk deploy
そうすると、ざっくりこんな感じの構成がAWS上に出来上がります
マネジメントコンソールのECSクラスター一覧から作成されたクラスターを確認する事が出来ます。
まだ何も作成していないの状態なので、サービスやタスク、EC2インスタンスは登録されていない状態です。
とりあえずコンテナを動かすためにEC2を追加してクラスターに取り込みましょう。
先程のCDKに cluster.addCapacity
を下記のように追加して、デプロイしていきます。
import * as cdk from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; export class EcsStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC作成 const vpc = new ec2.Vpc(this, 'ecs-vpc', { cidr: "10.0.0.0/16" }) // ECS Cluster作成 const cluster = new ecs.Cluster(this, 'cluster', { vpc }); // ClusterにAutoScalingGroupなt3.microを追加 cluster.addCapacity('DefaultAutoScalingGroupCapacity', { instanceType: new ec2.InstanceType("t3.micro"), desiredCapacity: 4 }); } }
$ cdk deploy
そうすると、AutoScaling Groupで作成されたEC2インスタンスが desiredCapacity
で指定した数起動してきます。
ECSのクラスター画面からもEC2インスタンスが表示されて、利用可能なCPU/メモリなどの状況が見れるようになります。
CDKでサクッと作ってしまいましたが、手動で作成される場合はAmazon ECS-optimized AMIsなどで提供されてるECS Agentが導入されているAMIを利用してEC2を起動して、UserData等でECS Agentに参加するクラスターの指定をする形となります。
現在の環境はこんな感じで、上記CDKコードで起動したインスタンスで起動する「ECS Agent」にUserData経由でECSクラスターに紐付けられている状態です。
タスク定義とサービスを作る
それでは仕上げに行きましょう。
「タスク定義」と「サービス」を作っていき、実際にコンテナをECSクラスター上で走らせて、サービスとALBを紐付けてコンテナにALB経由でアクセス出来るようにしましょう。
今回はHTTPでの疎通確認のテストを行いたいので、コンテナイメージは nginx:1.19.2-alpine
を利用します。(通常ECSを利用する場合は合わせてECRを利用するパターンが多いので、本番環境で利用する際は是非ECRもご活用下さい)
import * as cdk from '@aws-cdk/core'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; export class EcsStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC作成 const vpc = new ec2.Vpc(this, 'ecs-vpc', { cidr: "10.0.0.0/16" }) // ECS Cluster作成 const cluster = new ecs.Cluster(this, 'cluster', { vpc }); // ClusterにAutoScalingGroupなt3.microを追加 cluster.addCapacity('DefaultAutoScalingGroupCapacity', { instanceType: new ec2.InstanceType("t3.micro"), desiredCapacity: 4 }); // タスク定義作成 const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); // タスク定義設定 taskDefinition.addContainer('DefaultContainer', { image: ecs.ContainerImage.fromRegistry("nginx:1.19.2-alpine"), memoryLimitMiB: 256, cpu: 128 }).addPortMappings({ containerPort: 80 }); // ECS Service作成 const ecsService = new ecs.Ec2Service(this, 'Service', { cluster, taskDefinition }); // LoadBalancer作成 const loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'LoadBalancer', { vpc, internetFacing: true }); // TargetGroup作成 const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', { vpc, port: 80 }); // LoadBanacerと紐付け loadBalancer.addListener('Listener', { port: 80, defaultTargetGroups: [targetGroup] }) // TargetGroupとECS Serviceを紐付け targetGroup.addTarget(ecsService); } }
編集が終わったら毎度の事ながらcdk deployして環境に適用していきます。
$ cdk deploy
環境への適用が終わったら、マネジメントコンソールのECSからサービスとタスク定義が追加されていることを確認出来ます!
早速、マネジメントコンソールのALB画面からDNS名を拾ってブラウザでアクセスしてみます。
Welcome to Nginx! の画面が表示されました! nginxのコンテナにALB経由でアクセス出来ていることを確認出来ます!
コンテナイメージをアップデートしたときの挙動
さて、Hello Worldが済んだところでタスクアップデート時の挙動を見てみましょう。
上記CDKのコードでさらっと流してしまいましたが、CDKは内部的にコンテナのイメージ情報やポートマッピングの設定を記載する「タスク定義」と呼ばれるものをECSに登録して、サービスがタスク定義の内容を見ながらコンテナを作成をしたりALBなどとの連携をしています。
基本的にはタスク定義の新しいバージョンを作っていき、サービスからバージョンをアップデートしてタスクを更新していく形になります。
今回はCDKを利用する事を想定して、コンテナイメージを nginx:1.19.2-alpine
から nginx:1.19.2
に変更してみました。
// タスク定義作成 const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); // タスク定義設定 taskDefinition.addContainer('DefaultContainer', { image: ecs.ContainerImage.fromRegistry("nginx:1.19.2"), memoryLimitMiB: 256, cpu: 128 }).addPortMappings({ containerPort: 80 });
この状態で cdk deployして挙動を確認してみましょう。
$ cdk deploy
まずはじめに、新しいコンテナイメージが設定されたタスク定義が作成されていることを確認出来ます。
続いて、サービスのバージョンが上がり新しいタスク定義でタスク(コンテナ)が上がってきます。
ターゲットグループのヘルスチェックで合格してからしばらくすると、古いバージョンのタスクのDrainingが始まります。
このような流れで、最終的には設定したタスク数だけの新しいバージョンのタスクが立ち上がってきました。
「タスク定義を更新してサービスのタスクバージョンを変更」するだけでコンテナのローリングアップデートが可能で、ダウンタイムなしで簡単出来るので、非常に便利です。
まとめ
以上、『AWS 再入門ブログリレー 2020』の 15日目のエントリ『Amazon ECS』編でした。
明日 (8/25) は青柳さんの「Amazon Cognito」の予定です。お楽しみに!!